/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor;
import java.awt.Insets;
import java.awt.Color;
import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.Graphics;
import java.awt.Shape;
import java.awt.Rectangle;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.Position;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
/**
* Leaf view implementation. This corresponds and requires leaf element
* to be element for this view.
*
* The view has the following structure:
* +---------------------------------------------------------+
* | insets.top area | A
* | | | insets.top
* | | V
* | +--------------------------------------------------+
* | | | A
* | | | |
* | i | | |
* | n | | |
* | s | | |
* | e | | |
* | t | | |
* | s | | |
* | . | | |
* | l | | |
* | e | Main area of this view | | mainHeight
* | f | | |
* | t | | |
* | | | |
* | a | | |
* | r | | |
* | e | | |
* | a | | |
* | | | |
* | | | |
* | | | |
* | | | |
* | | | V
* | +--------------------------------------------------+
* | insets.bottom area | A
* | | | insets.bottom
* | | V
* +---------------------------------------------------------+
*
* @author Miloslav Metelka
* @version 1.00
*/
public class LeafView extends BaseView {
/** Height of the area this view manages excluding areas
* managed by its children and excluding insets.
*/
protected int mainHeight;
/** Draw graphics for converting position to coords */
ModelToViewDG modelToViewDG = new ModelToViewDG();
/** Draw graphics for converting coords to position */
ViewToModelDG viewToModelDG = new ViewToModelDG();
/** Construct new base view */
public LeafView(Element elem) {
super(elem);
}
/** Returns binary composition of paint areas */
protected int getPaintAreas(Graphics g, int clipY, int clipHeight) {
// invalid or empty height
if (clipHeight <= 0) {
return 0;
}
int clipEndY = clipY + clipHeight;
int startY = getStartY();
if (insets != null) { // valid insets
int mainAreaY = startY + insets.top;
if (clipEndY <= mainAreaY) {
return INSETS_TOP;
}
int bottomInsetsY = mainAreaY + mainHeight;
if (clipEndY <= bottomInsetsY) {
if (clipY <= mainAreaY) {
return INSETS_TOP + MAIN_AREA;
} else {
return MAIN_AREA;
}
}
if (clipY <= mainAreaY) {
return INSETS_TOP + MAIN_AREA + INSETS_BOTTOM;
} else if (clipY <= bottomInsetsY) {
return MAIN_AREA + INSETS_BOTTOM;
} else if (clipY <= bottomInsetsY + insets.bottom) {
return INSETS_BOTTOM;
} else {
return 0;
}
} else { // no insets
if (clipEndY <= startY || clipY >= startY + getHeight()) {
return 0;
} else {
return MAIN_AREA;
}
}
}
/** Paint either top insets, main area, or bottom insets depending on paintAreas variable */
protected void paintAreas(Graphics g, int clipY, int clipHeight, int paintAreas) {
if ((paintAreas & MAIN_AREA) == MAIN_AREA) {
ExtUI extUI = getExtUI();
int paintY = Math.max(clipY, 0); // relative start of area to paint
int startPos = getPosFromY(paintY);
int endPos = getPosFromY(clipY + clipHeight + (extUI.charHeight - 1));
int baseY;
try {
baseY = getYFromPos(startPos);
Drawer.getDrawer().draw(new Drawer.GraphicsDG(g),
extUI, startPos, endPos,
getBaseX(baseY), baseY, Integer.MAX_VALUE);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
}
/** Get total height of this view */
public int getHeight() {
if (insets != null) {
return insets.top + mainHeight + insets.bottom;
} else {
return mainHeight;
}
}
/** Compute and update main area height */
public void updateMainHeight() {
LeafElement elem = (LeafElement)getElement(); // need leaf element
try {
int lineDiff = (elem.getEndMark().getLine() - elem.getStartMark().getLine()
+ 1);
mainHeight = lineDiff * getExtUI().charHeight;
} catch (InvalidMarkException e) {
mainHeight = 0;
}
}
/** Get begin of line position from y-coord.
* If the position is before main area begining
* it returns start position. If it's beyond the end of view it returns
* end position.
* @param y y-coord to inspect
* always returns startOffset for y < start of main area
* @param eol means to return end of specified line instead of begining
* @return position in the document
*/
protected int getPosFromY(int y) {
// relative distance from begining of main area
int relY = y - getStartY() - ((insets != null) ? insets.top : 0);
if (relY < 0) { // before the view
return getStartOffset();
}
if (relY >= mainHeight) { // beyond the view
return getEndOffset();
}
int line = 0;
// get the begining line of the element
try {
line = ((BaseElement)getElement()).getStartMark().getLine();
} catch (InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
// advance the line by by relative distance
line += relY / getExtUI().charHeight;
int startOffset = getStartOffset();
int pos;
pos = Utilities.getRowStartFromLineOffset(((BaseDocument)getDocument()), line);
if (pos == -1) {
pos = startOffset;
}
return Math.max(pos, startOffset);
}
public int getBaseX(int y) {
return getExtUI().textMargin.left + ((insets != null) ? insets.left : 0);
}
/** Returns the number of child views in this view. */
public final int getViewCount() {
return 0;
}
/** Gets the n-th child view. */
public final View getView(int n) {
return null;
}
/** !!! osetrit konec view -> jump na dalsi v branchview */
public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
int direction, Position.Bias[] biasRet)
throws BadLocationException {
if (biasRet != null) {
biasRet[0] = Position.Bias.Forward;
}
switch (direction) {
case NORTH:
{
try {
BaseDocument doc = (BaseDocument)getDocument();
int visCol = doc.op.getVisColFromPos(pos);
pos = doc.op.getOffsetFromVisCol(visCol, doc.op.getBOLRelLine(pos, -1));
} catch (BadLocationException e) {
// leave the original position
}
return pos;
}
case SOUTH:
{
try {
BaseDocument doc = (BaseDocument)getDocument();
int visCol = doc.op.getVisColFromPos(pos);
pos = doc.op.getOffsetFromVisCol(visCol, doc.op.getBOLRelLine(pos, 1));
} catch (BadLocationException e) {
// leave the original position
}
return pos;
}
case WEST:
return (pos == -1) ? getStartOffset() : (pos - 1);
case EAST:
return (pos == -1) ? getEndOffset() : (pos + 1);
default:
throw new IllegalArgumentException("Bad direction: " + direction); // NOI18N
}
}
/** Get y coordinate from position.
* The position can lay anywhere inside this view.
*/
protected int getYFromPos(int pos) throws BadLocationException {
int relLine = 0;
try {
relLine = ((BaseDocument)getDocument()).op.getLine(pos)
- ((BaseElement)getElement()).getStartMark().getLine();
} catch (InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
return getStartY() + ((insets != null) ? insets.top : 0)
+ relLine * getExtUI().charHeight;
}
public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
ExtUI extUI = getExtUI();
Rectangle r = new Rectangle(0, 0, extUI.spaceWidths[0], extUI.charHeight);
BaseDocument doc = (BaseDocument)getDocument();
r.y = getYFromPos(pos);
if (extUI.superFixedFont) { // all chars in all styles have same width
r.x = getBaseX(r.y) + doc.op.getVisColFromPos(pos) * extUI.spaceWidths[0];
return r;
} else { // not superfixed font
try {
synchronized (modelToViewDG) {
modelToViewDG.setRect(r);
int bolPos = doc.op.getBOL(pos);
int eolPos = doc.op.getEOL(pos);
Drawer.getDrawer().draw(modelToViewDG, extUI, bolPos, eolPos,
getBaseX(r.y), 0, pos);
}
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
return r;
}
}
public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1,
Shape a) throws BadLocationException {
Rectangle r0 = (Rectangle)modelToView(p0, a, b0);
Rectangle r1 = (Rectangle)modelToView(p1, a, b1);
if (r0.y != r1.y) {
// If it spans lines, force it to be the width of the view.
r0.x = getComponent().getX();
r0.width = getComponent().getWidth();
}
r0.add(r1);
return r0;
}
public void modelToViewDG(int pos, Drawer.DrawGraphics dg)
throws BadLocationException {
ExtUI extUI = getExtUI();
BaseDocument doc = (BaseDocument)getDocument();
int y = getYFromPos(pos);
Drawer.getDrawer().draw(dg, extUI, doc.op.getBOL(pos),
doc.op.getEOL(pos), getBaseX(y), y, pos);
}
/** Get position from location on screen.
* @param x the X coordinate >= 0
* @param y the Y coordinate >= 0
* @param a the allocated region to render into
* @return the location within the model that best represents the
* given point in the view >= 0
*/
public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
int intX = (int)x;
int intY = (int)y;
if (biasReturn != null) {
biasReturn[0] = Position.Bias.Forward;
}
int begMainY = getStartY() + ((insets != null) ? insets.top : 0);
if (intY < begMainY) { // top insets or before this view
return -1; // getStartOffset();
} else if (intY > begMainY + mainHeight) { // bottom insets or beyond
return getEndOffset();
} else { // inside the view
int pos = getPosFromY(intY); // first get BOL of target line
ExtUI extUI = getExtUI();
try {
if (extUI.superFixedFont) {
intX -= extUI.textMargin.left; // substract possible line numbering width
if (intX > 0) {
pos = ((BaseDocument)getDocument()).op.getOffsetFromVisCol(
intX / getExtUI().spaceWidths[0], pos);
}
} else { // not superfixed font
int eolPos = ((BaseDocument)getDocument()).op.getEOL(pos);
synchronized (viewToModelDG) {
viewToModelDG.setX(intX);
viewToModelDG.pos = eolPos;
Drawer.getDrawer().draw(viewToModelDG, extUI, pos, eolPos,
getBaseX(intY), 0, -1);
pos = viewToModelDG.getOffset();
}
}
} catch (BadLocationException e) {
// return begining of line in this case
}
return pos;
}
}
/** Gives notification that something was inserted into the document
* in a location that this view is responsible for.
*
* @param e the change information from the associated document
* @param a the current allocation of the view
* @param f the factory to use to rebuild if the view has children
*/
public void insertUpdate(DocumentEvent evt, Shape a, ViewFactory f) {
if (evt.getLength() == 0) { // initial read was performed
updateMainHeight();
} else { // regular insertion (or undone removal)
try {
BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
ExtUI extUI = getExtUI();
int y = getYFromPos(evt.getOffset());
int lineHeight = extUI.charHeight;
if (bevt.getLFCount() > 0) { // one or more lines inserted
int addHeight = bevt.getLFCount() * lineHeight;
mainHeight += addHeight;
extUI.repaint(y);
}
int syntaxY = getYFromPos(bevt.getSyntaxUpdateOffset());
// !!! patch for case when DocMarksOp.eolMark is at the end of document
if (bevt.getSyntaxUpdateOffset() == evt.getDocument().getLength()) {
syntaxY += lineHeight;
}
if (getComponent().isShowing()) {
extUI.repaint(y, Math.max(lineHeight, syntaxY - y));
}
} catch (BadLocationException ex) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
ex.printStackTrace();
}
}
}
}
/** Gives notification from the document that attributes were removed
* in a location that this view is responsible for.
*
* @param e the change information from the associated document
* @param a the current allocation of the view
* @param f the factory to use to rebuild if the view has children
*/
public void removeUpdate(DocumentEvent evt, Shape a, ViewFactory f) {
try {
BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
ExtUI extUI = getExtUI();
int y = getYFromPos(evt.getOffset());
int lineHeight = extUI.charHeight;
if (bevt.getLFCount() > 0) { // one or more lines removed
int removeHeight = bevt.getLFCount() * lineHeight;
mainHeight -= removeHeight;
extUI.repaint(y);
}
int syntaxY = getYFromPos(bevt.getSyntaxUpdateOffset());
// !!! patch for case when DocMarksOp.eolMark is at the end of document
if (bevt.getSyntaxUpdateOffset() == evt.getDocument().getLength()) {
syntaxY += lineHeight;
}
if (getComponent().isShowing()) {
extUI.repaint(y, Math.max(lineHeight, syntaxY - y));
}
} catch (BadLocationException ex) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
ex.printStackTrace();
}
}
}
/** Attributes were changed in the are this view is responsible for.
* @param e the change information from the associated document
* @param a the current allocation of the view
* @param f the factory to use to rebuild if the view has children
*/
public void changedUpdate(DocumentEvent evt, Shape a, ViewFactory f) {
try {
if (getComponent().isShowing()) {
getExtUI().repaintBlock(evt.getOffset(), evt.getOffset() + evt.getLength());
}
} catch (BadLocationException ex) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
ex.printStackTrace();
}
}
}
/** Update view after changes were made to the model. */
protected void update(DocumentEvent e, Shape a, ViewFactory f) {
JTextComponent comp = getComponent();
boolean isShowing = comp.isShowing(); // !!! repaint based on this flag
BaseDocumentEvent evt = (BaseDocumentEvent)e;
int pos = evt.getOffset();
int docLen = comp.getDocument().getLength();
ExtUI extUI = getExtUI();
int charHeight = extUI.charHeight;
try {
int y = getYFromPos(pos);
if (evt.getType() == DocumentEvent.EventType.INSERT) {
if (evt.getLFCount() > 0) { // one or more lines inserted
int addHeight = evt.getLFCount() * charHeight;
mainHeight += addHeight;
extUI.repaint(y);
}
int syntaxY = getYFromPos(evt.getSyntaxUpdateOffset());
if (evt.getSyntaxUpdateOffset() == docLen) { // !!! patch for situation when DocMarksOp.eolMark is at the end of document
syntaxY += charHeight;
}
extUI.repaint(y, Math.max(charHeight, syntaxY - y));
} else if (evt.getType() == DocumentEvent.EventType.REMOVE) {
if (evt.getLFCount() > 0) { // one or more lines removed
int removeHeight = evt.getLFCount() * charHeight;
mainHeight -= removeHeight;
extUI.repaint(y);
}
int syntaxY = getYFromPos(evt.getSyntaxUpdateOffset());
if (evt.getSyntaxUpdateOffset() == docLen) { // !!! patch for situation when DocMarksOp.eolMark is at the end of document
syntaxY += charHeight;
}
extUI.repaint(y, Math.max(charHeight, syntaxY - y));
} else { // changed update
extUI.repaintBlock(pos, pos + evt.getLength()); // repaint that area
}
} catch (BadLocationException ex) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
ex.printStackTrace();
}
}
}
/** Get child view's y base value. Invalid in this case. */
protected int getViewStartY(BaseView view, int helperInd) {
return 0; // invalid in this case
}
static final class ModelToViewDG extends Drawer.AbstractDG {
Rectangle curRect;
void setRect(Rectangle curRect) {
this.curRect = curRect;
}
public boolean targetPosReached(int pos, char ch, int x, int y,
int charWidth, DrawContext ctx, Font previousFont) {
curRect.x = x;
return false;
}
}
static final class ViewToModelDG extends Drawer.AbstractDG {
int targetX;
int pos;
void setX(int targetX) {
this.targetX = targetX;
}
int getOffset() {
return pos;
}
public boolean targetPosReached(int pos, char ch, int x, int y,
int charWidth, DrawContext ctx, Font previousFont) {
if (x + charWidth < targetX) {
this.pos = pos;
return true;
} else { // x + charWidth >= targetX
this.pos = pos;
if (ch != '\n' && targetX >= x + charWidth / 2) {
Document doc = ctx.getExtUI().getDocument();
if (doc != null && pos + 1 <= doc.getLength()) {
this.pos = pos + 1;
}
}
return false;
}
}
}
}
/*
* Log
* 29 Gandalf-post-FCS1.23.1.4 4/18/00 Miloslav Metelka cursor move fix
* 28 Gandalf-post-FCS1.23.1.3 4/17/00 Miloslav Metelka fixed ghost tabs
* 27 Gandalf-post-FCS1.23.1.2 4/17/00 Miloslav Metelka unselecting empty line
* 26 Gandalf-post-FCS1.23.1.1 4/3/00 Miloslav Metelka undo update
* 25 Gandalf-post-FCS1.23.1.0 3/8/00 Miloslav Metelka
* 24 Gandalf 1.23 1/13/00 Miloslav Metelka
* 23 Gandalf 1.22 1/10/00 Miloslav Metelka
* 22 Gandalf 1.21 12/28/99 Miloslav Metelka
* 21 Gandalf 1.20 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 20 Gandalf 1.19 10/10/99 Miloslav Metelka
* 19 Gandalf 1.18 10/8/99 Miloslav Metelka stability improvements
* 18 Gandalf 1.17 9/10/99 Miloslav Metelka
* 17 Gandalf 1.16 8/27/99 Miloslav Metelka
* 16 Gandalf 1.15 7/2/99 Miloslav Metelka
* 15 Gandalf 1.14 6/29/99 Miloslav Metelka Scrolling and patches
* 14 Gandalf 1.13 6/25/99 Miloslav Metelka from floats back to ints
* 13 Gandalf 1.12 6/1/99 Miloslav Metelka
* 12 Gandalf 1.11 5/15/99 Miloslav Metelka fixes
* 11 Gandalf 1.10 5/13/99 Miloslav Metelka
* 10 Gandalf 1.9 5/7/99 Miloslav Metelka line numbering and fixes
* 9 Gandalf 1.8 5/5/99 Miloslav Metelka
* 8 Gandalf 1.7 4/23/99 Miloslav Metelka Undo added and internal
* improvements
* 7 Gandalf 1.6 4/8/99 Miloslav Metelka
* 6 Gandalf 1.5 3/30/99 Miloslav Metelka
* 5 Gandalf 1.4 3/27/99 Miloslav Metelka
* 4 Gandalf 1.3 3/18/99 Miloslav Metelka
* 3 Gandalf 1.2 2/13/99 Miloslav Metelka
* 2 Gandalf 1.1 2/9/99 Miloslav Metelka
* 1 Gandalf 1.0 2/3/99 Miloslav Metelka
* $
*/